﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Xml.Serialization;

namespace Microsoft.WowAddonStudio.FrameXml.Serialization.Support
{
	/// <summary>
	/// 
	/// </summary>
	/// <typeparam name="T"></typeparam>
    public class InheritableList<T> : ICollection<T> where T : SerializationObject
    {
        protected readonly List<T> list = new List<T>();

        private readonly PropertyBag owner;
        private readonly string propertyName;

        /// <summary>
        /// Initializes a new instance of the <see cref="InheritableList&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="owner">The property bag that owns the inheritable list.</param>
        /// <param name="propertyName">Name of the property.</param>
        public InheritableList(PropertyBag owner, string propertyName)
        {
            if (owner == null)
                throw new ArgumentNullException("owner");
            if (propertyName == null)
                throw new ArgumentNullException("propertyName");

            this.owner = owner;
            this.propertyName = propertyName;
        }

		/// <summary>
		/// Gets or sets a value indicating whether inheritance is enabled.
		/// </summary>
		/// <remarks>
		/// The value is propagated to all non-inherited children.
		/// </remarks>
		[XmlIgnore]
		[Browsable(false)]
		public virtual bool InheritanceEnabled
		{
			get { return owner.InheritanceEnabled; }
			set
			{
				owner.InheritanceEnabled = value;
				if (owner.InheritedPropertyBag != null) 
					owner.InheritedPropertyBag.InheritanceEnabled = value;
			}
		}

        /// <summary>
        /// Adds a <see cref="T"/> to the list.
        /// </summary>
        /// <param name="item">The item to add.</param>
        public void Add(T item)
        {
            list.Add(item);
        }

        /// <summary>
        /// Clears all items from the list.
        /// </summary>
        public void Clear()
        {
            list.Clear();
        }

		/// <summary>
		/// Determines whether the <see cref="T:System.Collections.Generic.ICollection`1"/> contains a specific value.
		/// </summary>
		/// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param>
		/// <returns>
		/// true if <paramref name="item"/> is found in the <see cref="T:System.Collections.Generic.ICollection`1"/>; otherwise, false.
		/// </returns>
        public bool Contains(T item)
        {
            return list.Contains(item);
        }
        
        /// <summary>
        /// Copies the elements of the <see cref="T:System.Collections.Generic.ICollection`1"/> to an <see cref="T:System.Array"/>, starting at a particular <see cref="T:System.Array"/> index.
        /// </summary>
        /// <param name="array">The one-dimensional <see cref="T:System.Array"/> that is the destination of the elements copied from <see cref="T:System.Collections.Generic.ICollection`1"/>. The <see cref="T:System.Array"/> must have zero-based indexing.</param>
        /// <param name="arrayIndex">The zero-based index in <paramref name="array"/> at which copying begins.</param>
        /// <exception cref="T:System.ArgumentNullException">
        /// 	<paramref name="array"/> is null.
        /// </exception>
        /// <exception cref="T:System.ArgumentOutOfRangeException">
        /// 	<paramref name="arrayIndex"/> is less than 0.
        /// </exception>
        /// <exception cref="T:System.ArgumentException">
        /// 	<paramref name="array"/> is multidimensional.
        /// -or-
        /// <paramref name="arrayIndex"/> is equal to or greater than the length of <paramref name="array"/>.
        /// -or-
        /// The number of elements in the source <see cref="T:System.Collections.Generic.ICollection`1"/> is greater than the available space from <paramref name="arrayIndex"/> to the end of the destination <paramref name="array"/>.
        /// -or-
        /// Type <paramref name="T"/> cannot be cast automatically to the type of the destination <paramref name="array"/>.
        /// </exception>
        public void CopyTo(T[] array, int arrayIndex)
        {
            list.CopyTo(array, arrayIndex);
        }

		/// <summary>
		/// Finds the specified match.
		/// </summary>
		/// <param name="match">The match.</param>
		/// <returns></returns>
        public T Find(Predicate<T> match)
        {
            return list.Find(match);
        }

		/// <summary>
		/// Finds the name of the by.
		/// </summary>
		/// <param name="name">The name.</param>
		/// <returns></returns>
        public T FindByName(string name)
        {
            if (name == null)
                throw new ArgumentNullException("name");

				return (T)list.OfType<INamedObject>()
				          	.SingleOrDefault(namedSerializationObject => namedSerializationObject.Name == name);
        }

        /// <summary>
        /// Removes the first occurence of an item from the list.
        /// </summary>
        /// <param name="item">The item to remove.</param>
        /// <returns>True, if the item was removed; False otherwise.</returns>
        public bool Remove(T item)
        {
            return list.Remove(item);
        }

        /// <summary>
        /// Gets the number of elements in this list, not counting the inherited elements.
        /// </summary>
	    public int UninheritedCount
	    {
            get { return GetEnumerable(false).Count(); }
	    }

        /// <summary>
        /// Gets the number of elements contained in the list.
        /// </summary>
        public int Count
        {
            get { return GetEnumerable(true).Count(); }
        }

        /// <summary>
        /// Gets a value indicating whether the list is read-only.
        /// </summary>
        /// <value></value>
        /// <returns>true if the list is read-only; otherwise, false.</returns>
        public bool IsReadOnly
        {
            get { return false; }
        }

        /// <summary>
        /// Gets or sets the <see cref="T"/> at the specified index.
        /// </summary>
        /// <value></value>
        public T this[int index]
        {
            get { return list[index]; }
            set { list[index] = value; }
        }

        /// <summary>
        /// Returns an enumerator that iterates through a collection.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
        /// </returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        /// <summary>
        /// Returns an enumerator that iterates through the collection.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
        /// </returns>
        public IEnumerator<T> GetEnumerator()
        {
            return this.GetEnumerable(true).GetEnumerator();
        }

        /// <summary>
        /// Gets an enumerable list of the items, optionally including the inherited items.
        /// </summary>
        /// <param name="includeInherited">A value indicating whether to include the inherited items.</param>
        /// <returns>An instance of the <see cref="IEnumerator{T}"/> generic interface.</returns>
        public virtual IEnumerable<T> GetEnumerable(bool includeInherited)
        {
            if (includeInherited && owner.InheritanceEnabled && owner.InheritedPropertyBag != null && InheritedList != null)
            {
                var uniqueInheritedList = new List<T>();

                foreach (var inheritedItem in InheritedList)
                {
                    // If we are dealing with named objects that are inheritable, look up same name in our list
                    if (inheritedItem is INamedObject && inheritedItem is IInheritableObject<T>)
                    {
                        // Find item in our list by the same name
                        var listItem = this.FindByName(((INamedObject)inheritedItem).Name);
                        if (listItem != null)
                        {
                            // Update link between the list item and the inherited item
                            ((IInheritableObject<T>)listItem).InheritedObject = inheritedItem;

                            // Do not add inherited item, rather continue as our list will be concatenated at the end
                            continue;
                        }
                    }

                    // Simply add inherited item to the list of 'unique' inherited items
                    uniqueInheritedList.Add(inheritedItem);
                }

                // Concatenate unique inherited list with our list
                return uniqueInheritedList.Concat(list);
            }

            return list;
        }

        /// <summary>
        /// Gets the inherited list.
        /// </summary>
        protected InheritableList<T> InheritedList
        {
            get
            {
                // Return inherited list if inheritance is enabled and we have an inherited property bag
                if (owner.InheritanceEnabled && owner.InheritedPropertyBag != null)
                    return owner.InheritedPropertyBag.GetInheritableList<T>(propertyName);

                return null;
            }
        }
    }
}
